/*
 * DjvuDroidBridge.cpp
 *
 *  Created on: 17.01.2010
 *      Author: Cool
 */

#include <jni.h>
#include <stdlib.h>
#include <DjvuDroidTrace.h>
#include <ddjvuapi.h>
#include <miniexp.h>

void ThrowError(JNIEnv* env, const char* msg)
{
    jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
    if (!exceptionClass)
    	return;
    if (!msg)
    	env->ThrowNew(exceptionClass, "Djvu decoding error!");
    else
	env->ThrowNew(exceptionClass, msg);
}


void ThrowDjvuError(JNIEnv* env, const ddjvu_message_t* msg)
{
    if (!msg || !msg->m_error.message)
	ThrowError(env, "Djvu decoding error!");
    else
	ThrowError(env, msg->m_error.message);
}


#define HANDLE_TO_DOC(handle) (ddjvu_document_t*)handle
#define HANDLE(ptr) (jlong)ptr

extern "C" jlong
Java_org_vudroidplus_djvudroid_codec_DjvuContext_create(JNIEnv *env, jclass cls)
{
    ddjvu_context_t* context = ddjvu_context_create(DJVU_DROID);
    DEBUG_PRINT("Creating context: %x", context);
    return (jlong) context;
}

extern "C" void
Java_org_vudroidplus_djvudroid_codec_DjvuContext_free(JNIEnv *env,
                                    jclass cls,
                                    jlong contextHandle)
{
    ddjvu_context_release((ddjvu_context_t *)contextHandle);
}

bool number_from_miniexp(miniexp_t sexp, int *number)
{
    if (miniexp_numberp (sexp)) 
    {
	*number = miniexp_to_int (sexp);
	return TRUE;
    } 
    else 
    {
	return FALSE;
    }
}

bool string_from_miniexp(miniexp_t sexp, const char **str)
{
    if (miniexp_stringp (sexp)) 
    {
	*str = miniexp_to_str (sexp);
	return TRUE;
    } 
    else 
    {
	return FALSE;
    }
}


jint* get_djvu_hyperlink_area(ddjvu_pageinfo_t *page_info, miniexp_t sexp, int &type, int &len)
{    
    miniexp_t iter;

    iter = sexp;

    DEBUG_PRINT("Hyperlink area %s", miniexp_to_name(miniexp_car(sexp)));
	
    if(miniexp_car(iter) == miniexp_symbol("rect"))
	type = 1;
    else 
    if(miniexp_car(iter) == miniexp_symbol("oval"))
    	type = 2;
    else 
    if (miniexp_car(iter) == miniexp_symbol("poly"))
    	type = 3;
    else 
	return NULL; 

    
    len = miniexp_length(iter);
    jint* array = new jint[len];
    
    int x, i = 0;
    iter = miniexp_cdr(iter);
    while (iter != miniexp_nil) 
    {    
	if (!number_from_miniexp (miniexp_car(iter), &x)) break;
	iter = miniexp_cdr(iter);	
	array[i++] = (jint)x;
	if(i>=len) break;
    }
    
    len = i;
    if((type == 1 || type == 2) && len == 4)
    {
	int miny, width, height;
	
	miny = array[1];
	width = array[2];
	height = array[3];
	array[1] = (page_info->height - (miny + height));
	array[2] = array[0] + width;
	array[3] = (page_info->height - miny);
    }
    if(type == 3  && (len % 2) == 0)
    {
	int ccc;
	for(int k = 1; k<len; k+=2)
	{
	    ccc = array[k];
	    array[k] = (page_info->height - ccc);
	}
    }

    return array;		
}



jobject get_djvu_hyperlink_mapping(JNIEnv *jenv, ddjvu_document_t* djvu_document, ddjvu_pageinfo_t *page_info, miniexp_t sexp)
{
    miniexp_t iter;
    const char *url, *url_target;
    
    jobject hl = NULL;

    iter = sexp;
	
    if (miniexp_car(iter) != miniexp_symbol("maparea")) 
    {
	DEBUG_PRINT("DjvuLibre error: Unknown hyperlink %s", miniexp_to_name(miniexp_car(sexp)));
	return hl;
    }
	
    iter = miniexp_cdr(iter);
	
    if(miniexp_caar(iter) == miniexp_symbol("url")) 
    {
	if(!string_from_miniexp(miniexp_cadr(miniexp_car(iter)), &url))
	{
	    DEBUG_PRINT("DjvuLibre error: Unknown hyperlink %s", miniexp_to_name(miniexp_car(sexp)));
	    return hl;
	}
	if(!string_from_miniexp(miniexp_caddr(miniexp_car(iter)), &url_target))
	{
	    DEBUG_PRINT("DjvuLibre error: Unknown hyperlink %s", miniexp_to_name(miniexp_car(sexp)));
	    return hl;
	}
    } 
    else 
    {
	if (!string_from_miniexp(miniexp_car(iter), &url))
	{
	    DEBUG_PRINT("DjvuLibre error: Unknown hyperlink %s", miniexp_to_name(miniexp_car(sexp)));
	    return hl;
	}
	url_target = NULL;
    }

    iter = miniexp_cdr(iter);
    /* FIXME: DjVu hyperlink comments are ignored */
	
    int len = 0;
    int type;
    jint* data;
    iter = miniexp_cdr(iter);
    if((data = get_djvu_hyperlink_area(page_info, miniexp_car(iter),type,len)) == NULL)
    {
	DEBUG_PRINT("DjvuLibre error: Unknown hyperlink %s", miniexp_to_name(miniexp_car(sexp)));
	return hl;
    }

    iter = miniexp_cdr (iter);
    /* FIXME: DjVu hyperlink attributes are ignored */


    DEBUG_PRINT("DjvuLibre: Hyperlink url: %s url_target: %s", url, url_target);
    
    if(!url) 
    {
	delete [] data;
	return hl;
    }
    
    jclass pagelinkClass = jenv->FindClass("org/vudroidplus/core/PageLink");
    if(!pagelinkClass)
    {
	delete [] data;
	return hl;
    }

        
    jmethodID plInitMethodId = jenv->GetMethodID(pagelinkClass, "<init>", "(Ljava/lang/String;I[I)V");
    if(!plInitMethodId) 
    {
	delete [] data;
	return hl;
    }
                
    jintArray points = jenv->NewIntArray(len);                                                                    
    jenv->SetIntArrayRegion(points, 0, len, data);    
    
    jstring jstr = jenv->NewStringUTF(url);

    hl = jenv->NewObject(pagelinkClass, plInitMethodId, jstr, (jint)type ,points);
    
    jenv->DeleteLocalRef(jstr);
    jenv->DeleteLocalRef(points);
    
    delete [] data;
    
//    DEBUG_PRINT("DjvuLibre: Hyperlink url: %s url_target: %s", url, url_target);
    
    return hl;
			
}


jobject djvu_links_get_links(JNIEnv *jenv, ddjvu_document_t* djvu_document, int page)
{

    DEBUG_PRINT("djvu_links_get_links %d", page);

    miniexp_t page_annotations = miniexp_nil;
    miniexp_t *hyperlinks = NULL, *iter = NULL;
    ddjvu_pageinfo_t page_info;


    jobject arrayList = NULL;
	
    page_annotations = ddjvu_document_get_pageanno(djvu_document, page);
    
    ddjvu_document_get_pageinfo(djvu_document, page, &page_info);
    	
    if(page_annotations) 
    {
	hyperlinks = ddjvu_anno_get_hyperlinks(page_annotations);
	if(hyperlinks) 
	{
	
	    jclass arrayListClass = jenv->FindClass("java/util/ArrayList");
	    if(!arrayListClass)
	        return arrayList;
	        
	    jmethodID alInitMethodId = jenv->GetMethodID(arrayListClass, "<init>", "()V");
	    if(!alInitMethodId)
	        return arrayList;

	    jmethodID alAddMethodId = jenv->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
	    if(!alAddMethodId)
	        return arrayList;
            
	    arrayList = jenv->NewObject(arrayListClass, alInitMethodId);
	    if(!arrayList)
	        return arrayList;

	    for(iter = hyperlinks; *iter; ++iter) 
	    {
		jobject hl = get_djvu_hyperlink_mapping(jenv, djvu_document, &page_info, *iter);
		if(hl)
		    jenv->CallBooleanMethod(arrayList, alAddMethodId, hl);
		//jenv->DeleteLocalRef(hl);
	    }
	    free(hyperlinks);
	}
	ddjvu_miniexp_release(djvu_document, page_annotations);
    }
    return arrayList;
}




extern "C" jlong
Java_org_vudroidplus_djvudroid_codec_DjvuDocument_open(JNIEnv *env,
                                    jclass cls,
                                    jlong contextHandle,
                                    jstring fileName)
{
    const char* fileNameString = env->GetStringUTFChars(fileName, NULL);
    DEBUG_PRINT("Opening document: %s", fileNameString);
    jlong docHandle = (jlong)(ddjvu_document_create_by_filename((ddjvu_context_t*)(contextHandle), fileNameString, FALSE));
    env->ReleaseStringUTFChars(fileName, fileNameString);
    if(!docHandle)
	ThrowError(env,"DJVU file not found or corrupted.");
//    if(docHandle)
//    {
//	char *s = ddjvu_document_get_filedump((ddjvu_document_t*)docHandle, 1);
//        DEBUG_PRINT("%s",s);
//    }
    return docHandle;
}

void CallDocInfoCallback(JNIEnv* env, jobject thiz, const ddjvu_message_t* msg)
{
    DEBUG_WRITE("Calling handleDocInfo callback");
    jclass cls = env->GetObjectClass(thiz);
    if (!cls)
	return;
    jmethodID handleDocInfoId = env->GetMethodID(cls, "handleDocInfo", "()V");
    if (!handleDocInfoId)
    	return;
    env->CallVoidMethod(thiz, handleDocInfoId);
}

extern "C" void
Java_org_vudroidplus_djvudroid_codec_DjvuContext_handleMessage(JNIEnv *env,
                                    jobject thiz,
                                    jlong contextHandle)
{
    const ddjvu_message_t *msg;
    ddjvu_context_t* ctx = (ddjvu_context_t*)(contextHandle);
//    DEBUG_PRINT("handleMessage for ctx: %x",ctx);
    if(msg = ddjvu_message_peek(ctx))
    {
        switch (msg->m_any.tag)
        {
            case DDJVU_ERROR:
            	ThrowDjvuError(env, msg);
                break;
            case DDJVU_INFO:
                break;
            case DDJVU_DOCINFO:
            	CallDocInfoCallback(env, thiz, msg);
            	break;
            default:
                break;
        }
        ddjvu_message_pop(ctx);
	}
}

extern "C" jlong
Java_org_vudroidplus_djvudroid_codec_DjvuDocument_getPage(JNIEnv *env,
                                    jclass cls,
                                    jlong docHandle,
                                    jint pageNumber)
{
	DEBUG_PRINT("getPage num: %d", pageNumber);
	return (jlong)ddjvu_page_create_by_pageno((ddjvu_document_t*)docHandle, pageNumber);
}

extern "C" jobject 
Java_org_vudroidplus_djvudroid_codec_DjvuDocument_getPageLinks(JNIEnv *env,
                                    jclass cls,
                                    jlong docHandle,
                                    jint pageNumber)
{
	DEBUG_PRINT("getPageLinks num: %d", pageNumber);
	return djvu_links_get_links(env, (ddjvu_document_t*)docHandle, pageNumber);
}


extern "C" jint 
Java_org_vudroidplus_djvudroid_codec_DjvuDocument_getPageInfo(JNIEnv *env,
                                    jclass cls,
                                    jlong docHandle,
                                    jint pageNumber,
                                    jlong contextHandle,
                                    jobject cpi)
{
    ddjvu_status_t r;
    ddjvu_pageinfo_t info;

    jclass clazz;
    jfieldID fid;

    while ((r=ddjvu_document_get_pageinfo((ddjvu_document_t*)docHandle,pageNumber,&info))<DDJVU_JOB_OK)
	Java_org_vudroidplus_djvudroid_codec_DjvuContext_handleMessage(env, cls, contextHandle);


    clazz = env->GetObjectClass(cpi);
    if (0 == clazz)
    {
	return(-1);
    }
    fid = env->GetFieldID(clazz,"width","I");

    // This next line is where the power is hidden. Directly change
    // even private fields within java objects. Nasty!
    env->SetIntField(cpi,fid,info.width);

    fid = env->GetFieldID(clazz,"height","I");
    env->SetIntField(cpi,fid,info.height);

    fid = env->GetFieldID(clazz,"dpi","I");
    env->SetIntField(cpi,fid,info.dpi);

    fid = env->GetFieldID(clazz,"rotation","I");
    env->SetIntField(cpi,fid,info.rotation);

    fid = env->GetFieldID(clazz,"version","I");
    env->SetIntField(cpi,fid,info.version);

    return 0;
}

extern "C" void
Java_org_vudroidplus_djvudroid_codec_DjvuDocument_free(JNIEnv *env,
                                    jclass cls, jlong docHandle)
{
    ddjvu_document_release((ddjvu_document_t*)docHandle);
}

extern "C" jint
Java_org_vudroidplus_djvudroid_codec_DjvuDocument_getPageCount(JNIEnv *env,
                                    jclass cls, jlong docHandle)
{
	return ddjvu_document_get_pagenum(HANDLE_TO_DOC(docHandle));
}

extern "C" jboolean
Java_org_vudroidplus_djvudroid_codec_DjvuPage_isDecodingDone(JNIEnv *env,
                                    jclass cls, jlong pageHandle)
{
	return ddjvu_page_decoding_done((ddjvu_page_t*)pageHandle);
}

extern "C" jint
Java_org_vudroidplus_djvudroid_codec_DjvuPage_getWidth(JNIEnv *env,
                                    jclass cls, jlong pageHangle)
{
	return ddjvu_page_get_width((ddjvu_page_t*)pageHangle);
}

extern "C" jint
Java_org_vudroidplus_djvudroid_codec_DjvuPage_getHeight(JNIEnv *env,
                                    jclass cls, jlong pageHangle)
{
	return ddjvu_page_get_height((ddjvu_page_t*)pageHangle);
}

extern "C" jboolean
Java_org_vudroidplus_djvudroid_codec_DjvuPage_renderPage(JNIEnv *env,
                                    jclass cls,
                                    jlong pageHangle,
                                    jint targetWidth,
                                    jint targetHeight,
                                    jfloat pageSliceX,
                                    jfloat pageSliceY,
                                    jfloat pageSliceWidth,
                                    jfloat pageSliceHeight,
                                    jintArray buffer)
{


    DEBUG_WRITE("Rendering page");
    ddjvu_page_t* page = (ddjvu_page_t*)((pageHangle));
    ddjvu_rect_t pageRect;
    pageRect.x = 0;
    pageRect.y = 0;
    pageRect.w = targetWidth / pageSliceWidth;
    pageRect.h = targetHeight / pageSliceHeight;
    ddjvu_rect_t targetRect;
    targetRect.x = pageSliceX * targetWidth / pageSliceWidth;
    targetRect.y = pageSliceY * targetHeight / pageSliceHeight;
    targetRect.w = targetWidth;
    targetRect.h = targetHeight;
    unsigned int masks[] = {0xFF0000, 0x00FF00, 0x0000FF};
    ddjvu_format_t* pixelFormat = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 3, masks);
    ddjvu_format_set_row_order(pixelFormat, TRUE);
    ddjvu_format_set_y_direction(pixelFormat, TRUE);
    
    char *pBuffer = (char *)env->GetPrimitiveArrayCritical(buffer, 0);
    jboolean result = ddjvu_page_render(page, DDJVU_RENDER_COLOR, &pageRect, &targetRect, pixelFormat, targetWidth * 4, pBuffer);
    env->ReleasePrimitiveArrayCritical(buffer, pBuffer, 0);
    
    ddjvu_format_release(pixelFormat);
    return result;
}

extern "C" void
Java_org_vudroidplus_djvudroid_codec_DjvuPage_free(JNIEnv *env,
                                    jclass cls, jlong pageHangle)
{
	ddjvu_page_release((ddjvu_page_t*)pageHangle);
}

//Outline
extern "C" jlong Java_org_vudroidplus_djvudroid_codec_DjvuOutline_open(JNIEnv *env,
                                    jclass cls, jlong docHandle)
{
//        DEBUG_PRINT("DjvuOutline.open(%p)",docHandle);
	miniexp_t outline = ddjvu_document_get_outline((ddjvu_document_t*)docHandle);
	if (outline && outline != miniexp_dummy)
	{
	    if (!miniexp_consp(outline) || miniexp_car(outline) != miniexp_symbol("bookmarks"))
	    {
	         DEBUG_PRINT("%s","Outline data is corrupted");
	         return 0;
	    }
	    else
		return (jlong)outline;
//	    debug_outline(outline);
	}
	return 0;
}

extern "C" jboolean Java_org_vudroidplus_djvudroid_codec_DjvuOutline_expConsp(JNIEnv *env,
                                    jclass cls, jlong expr)
{
//        DEBUG_PRINT("DjvuOutline.expConsp(%p)",expr);
	return miniexp_consp((miniexp_t)expr);
}

extern "C" jstring Java_org_vudroidplus_djvudroid_codec_DjvuOutline_getTitle(JNIEnv *env,
                                    jclass cls, jlong expr)
{
//        DEBUG_PRINT("DjvuOutline.getTitle(%p)",expr);
        miniexp_t s = miniexp_car((miniexp_t)expr);
        if (miniexp_consp(s) && miniexp_consp(miniexp_cdr(s)) &&
	    miniexp_stringp(miniexp_car(s)) && miniexp_stringp(miniexp_cadr(s)))
        {
              const char* buf = miniexp_to_str(miniexp_car(s));
              return env->NewStringUTF(buf);
        }
        return NULL;
}

extern "C" jstring Java_org_vudroidplus_djvudroid_codec_DjvuOutline_getLink(JNIEnv *env,
                                    jclass cls, jlong expr, jlong docHandle)
{
//        DEBUG_PRINT("DjvuOutline.getLinkPage(%p)",expr);
        miniexp_t s = miniexp_car((miniexp_t)expr);
        if (miniexp_consp(s) && miniexp_consp(miniexp_cdr(s)) && 
	    miniexp_stringp(miniexp_car(s)) && miniexp_stringp(miniexp_cadr(s)))
        {
    	    const char *link = miniexp_to_str(miniexp_cadr(s));
    	    int number = -1;
    	    if (link && link[0]=='#')
    	    {
    		number = ddjvu_document_search_pageno((ddjvu_document_t*)docHandle,link+1);
    		if (number >= 0)
		{
		    char linkbuf[128];
		    snprintf(linkbuf,127,"#%d",number+1);
		    return env->NewStringUTF(linkbuf);
		}
    	    }
	    return env->NewStringUTF(link);
        }
        return NULL;
}

extern "C" jlong Java_org_vudroidplus_djvudroid_codec_DjvuOutline_getNext(JNIEnv *env,
                                    jclass cls,jlong expr)
{
//    DEBUG_PRINT("DjvuOutline.getNext(%p)",expr);
    return (jlong)miniexp_cdr((miniexp_t)expr);
}

extern "C" jlong Java_org_vudroidplus_djvudroid_codec_DjvuOutline_getChild(JNIEnv *env,
                                    jclass cls, jlong expr)
{
//    DEBUG_PRINT("DjvuOutline.getChild(%p)",expr);
    miniexp_t s = miniexp_car((miniexp_t)expr);
    if (miniexp_consp(s) && miniexp_consp(miniexp_cdr(s)) && miniexp_stringp(miniexp_car(s)) && miniexp_stringp(miniexp_cadr(s)))
	return (jlong)miniexp_cddr(s);
    return 0;
}

